package com.yb.custom.annotation.log.util;

import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import javax.crypto.spec.SecretKeySpec;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Map;

/**
 * 使用jjwt-api依赖的新版--线程不可变的/线程安全的JwtParser来生成token验证token
 *
 * @author yangbiao
 * @date 2022/2/25
 */
@Slf4j
public class JwtUtils {
    /**
     * 请求来源APP端请求
     */
    public static final String APP = "APP";
    /**
     * JWT的token在Header里的key和jwt的前缀
     */
    public static final String JWT_BEARER = "Bearer ";
    /**
     * JTI前缀
     */
    public static final String JTI_PREFIX = "&jti&";
    /**
     * token的请求头名称
     */
    public static final String AUTHORIZATION = "Authorization";
    /**
     * JWT的随机密钥(最好加密处理)-->不能每次动态生成,否则验证签名密钥每个实例不一致,使用64位字符
     * 可以在配置文件里配置,然后这里获取配置文件里的配置,就可以通过环境变量的方式更换这个秘钥了(安全性需要考虑(加密))
     */
    public static final String JWT_SECRET = "k2104VR6E17846aebc95eg5C286c9bdaef7sb4703b4c4fb88Dee36c3UI2ea21a";
    /**
     * APP的刷新token的过期时间
     */
    public static final long APP_JWT_RT_EXPIRE_DAYS = 15;
    /**
     * PC的刷新token的过期时间(足以续期三次)
     */
    public static final long PC_JWT_RT_EXPIRE_MINUTES = 125;
    /**
     * JWT的token的过期时间
     */
    public static final long JWT_EXPIRE_MINUTES = 30;

    /**
     * 获取jwt唯一身份识别码jti
     *
     * @return jti
     */
    public static String createJti() {
        return String.valueOf(System.nanoTime());
    }

    /**
     * 获取使用Token的过期时间
     *
     * @return 使用Token的过期时间
     */
    public static LocalDateTime getAccessTokenExpireTime() {
        return LocalDateTime.now().plusSeconds(Duration.ofMinutes(JWT_EXPIRE_MINUTES).getSeconds());
    }

    //&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

    /**
     * 生成使用token信息(accessToken)
     *
     * @param user 没有敏感信息的用户信息包装类对象
     * @return
     */
    public static String createAccessToken(LoginUserUtils.LoginUser user) {
        final Duration duration = Duration.ofMinutes(JWT_EXPIRE_MINUTES);
        return createAccessToken(user, duration, JWT_SECRET, SignatureAlgorithm.HS512);
    }

    /**
     * 生成使用token信息(accessToken)
     *
     * @param user        没有敏感信息的用户信息包装类对象
     * @param expiredTime accessToken的过期时间
     * @param secretSalt  密钥盐
     * @param signature   加密算法
     * @return
     */
    public static String createAccessToken(LoginUserUtils.LoginUser user, Duration expiredTime,
                                           String secretSalt, SignatureAlgorithm signature) {
        //添加构成JWT的参数
        JwtBuilder builder = Jwts.builder()
                .setHeaderParam("type", "JWT")
                //装填用户信息到荷载(切记实测不能使用setPayload方法)
                .addClaims((Map<String, Object>) JSONObject.toJSON(user))
                //设置subject
                .setSubject(user.getUsername())
                //设置签名信息
                .signWith(new SecretKeySpec(secretSalt.getBytes(), signature.getJcaName()), signature);
        //设置Token过期时间
        if (expiredTime.getSeconds() > 0) {
            Instant instant = LocalDateTime.now().plusSeconds(expiredTime.getSeconds())
                    .atZone(ZoneId.systemDefault()).toInstant();
            builder.setExpiration(Date.from(instant)).setNotBefore(new Date());
        }
        //生成JWT并为其加上前缀Bearer
        return JWT_BEARER.concat(builder.compact());
    }

    /**
     * 验证使用token的合法性(返回对象不为null表示认证通过)
     *
     * @param jwtToken jwt生成的token
     * @return loginUser对象
     */
    public static LoginUserUtils.LoginUser verifyAccessToken(String jwtToken) {
        return verifyAccessToken(jwtToken, JWT_SECRET, SignatureAlgorithm.HS512);
    }

    /**
     * 验证使用token的合法性(返回对象不为null表示认证通过)
     *
     * @param jwtToken   jwt生成的token
     * @param secretSalt 密钥盐--可以每个用户持有一个自己的密钥存储在用户信息中(默认UUID进行Base64加密)
     * @param signature  加密算法
     * @return loginUser对象
     */
    public static LoginUserUtils.LoginUser verifyAccessToken(String jwtToken, String secretSalt, SignatureAlgorithm signature) {
        //判断token的合法性
        if (StringUtils.hasText(jwtToken) && jwtToken.startsWith(JWT_BEARER)) {
            //去掉头部的Bearer 前缀
            String accessToken = jwtToken.replaceFirst(JWT_BEARER, "");
            //获取加密key信息
            final SecretKeySpec key = new SecretKeySpec(secretSalt.getBytes(), signature.getJcaName());
            //这个新的使用方式(使用的是jjwt-api而不是jjwt依赖),之前的那个过时了,且这个获取的是线程不可变的/线程安全的JwtParser
            final boolean signedResult = Jwts.parserBuilder().setSigningKey(key).build().isSigned(jwtToken);
            //验证jwtToken的签名信息
            if (signedResult) {
                try {
                    //如果token过期,调用如下api将抛出ExpiredJwtException异常
                    //解析并获取Claims对象(由于设置了iss,过期时间等信息,这里claims不会为空,它继承了Map<String, Object>)
                    Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody();
                    //处理数据
                    JSONObject jsonObject = (JSONObject) JSONObject.toJSON(claims);
                    //将jsonObject解析成LoginUser对象(其实就是把里面的和LoginUser对应字段的信息封装起来)
                    LoginUserUtils.LoginUser loginUser = JSONObject.toJavaObject(jsonObject, LoginUserUtils.LoginUser.class);
                    //设置token过期时间到线程的对象里
                    final LocalDateTime localDateTime = Instant.ofEpochMilli(claims.getExpiration().getTime())
                            .atZone(ZoneId.systemDefault()).toLocalDateTime();
                    loginUser.setTokenExpireTime(localDateTime);
                    //返回解析后的信息
                    return loginUser;
                } catch (ExpiredJwtException e) {
                    //这个在过滤器那里解析token的时候,抛出异常会导致security放过的接口都是无法访问
                    log.error("使用token已经过期={}", e.getMessage(), e);
                } catch (Exception e) {
                    log.error("使用token合法性验证未通过={}", e.getMessage(), e);
                }
            }
        }
        return null;
    }

    //&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

    /**
     * 使用用户ID生成的刷新token信息(refreshToken)
     *
     * @param usernameOrUserId 用户名(唯一)
     * @param jti              token的唯一标识(ID)
     * @return
     */
    public static String createRefreshToken(String usernameOrUserId, String jti) {
        return createRefreshToken(usernameOrUserId, jti, Duration.ofDays(PC_JWT_RT_EXPIRE_MINUTES),
                JWT_SECRET, SignatureAlgorithm.HS512);
    }


    /**
     * 使用用户ID生成的刷新token信息(refreshToken)
     *
     * @param usernameOrUserId 用户唯一标识(用户名唯一)
     * @param expiredTime      refreshToken的过期时间
     * @param secretSalt       密钥盐
     * @param signature        加密算法
     * @param jti              token的唯一标识(ID)
     * @return
     */
    public static String createRefreshToken(String usernameOrUserId, String jti, Duration expiredTime,
                                            String secretSalt, SignatureAlgorithm signature) {
        //添加构成JWT的参数
        JwtBuilder builder = Jwts.builder()
                .setHeaderParam("type", "JWT")
                //声明信息
                .claim("scope", "Refresh Token")
                //添加jwt的id,也就是jti(用于确认是否是最近生成的token)
                .setId(jti)
                //设置主体名称
                .setSubject(usernameOrUserId)
                //设置签名信息
                .signWith(new SecretKeySpec(secretSalt.getBytes(), signature.getJcaName()), signature);
        //添加Token过期时间
        if (expiredTime.getSeconds() > 0) {
            Instant instant = LocalDateTime.now().plusSeconds(expiredTime.getSeconds())
                    .atZone(ZoneId.systemDefault()).toInstant();
            builder.setExpiration(Date.from(instant)).setNotBefore(new Date());
        }
        //生成JWT并添加前缀Bearer
        return JWT_BEARER.concat(builder.compact());
    }

    /**
     * 验证刷新token的合法性(返回不为null则表示认证合法)
     *
     * @param refreshToken 刷新token
     * @param getJti       是否获取jti--(如果是true则是返回jti,如果是false则返回用户ID用户名(看存储的是哪个))
     * @return 用户ID
     */
    public static String verifyRefreshToken(String refreshToken, boolean getJti) {
        return verifyRefreshToken(refreshToken, JWT_SECRET, SignatureAlgorithm.HS512, getJti);
    }

    /**
     * 验证刷新token的合法性(返回不为null则表示认证合法)
     *
     * @param refreshToken 刷新token
     * @param secretSalt   密钥盐--可以每个用户持有一个自己的密钥存储在用户信息中(默认UUID进行Base64加密)
     * @param signature    加密算法
     * @param getJti       是否获取jti--(如果是true则是返回jti,如果是false则返回用户ID)
     * @return 用户ID
     */
    public static String verifyRefreshToken(String refreshToken, String secretSalt,
                                            SignatureAlgorithm signature, boolean getJti) {
        //判断token的合法性
        if (StringUtils.hasText(refreshToken) && refreshToken.startsWith(JWT_BEARER)) {
            //去掉头部的Bearer 前缀
            String jwtRefreshToken = refreshToken.replaceFirst(JWT_BEARER, "");
            //获取加密key信息
            final SecretKeySpec key = new SecretKeySpec(secretSalt.getBytes(), signature.getJcaName());
            //这个新的使用方式(使用的是jjwt-api而不是jjwt依赖),之前的那个过时了,且这个获取的是线程不可变的/线程安全的JwtParser
            final boolean signedResult = Jwts.parserBuilder().setSigningKey(key).build().isSigned(refreshToken);
            //验证jwtToken的签名信息(刷新token不能用来登录,只是过期时间很长的,用来验证合法性后,生成token的认证token)
            if (signedResult) {
                try {
                    //如果token过期,调用如下api将抛出ExpiredJwtException异常
                    Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(jwtRefreshToken).getBody();
                    //判断获取的是(用户ID或用户名)还是Jti
                    return getJti ? claims.getId() : claims.getSubject();
                } catch (ExpiredJwtException e) {
                    log.error("刷新token已经过期={}", e.getMessage(), e);
                } catch (Exception e) {
                    log.error("解析刷新token异常={}", e.getMessage(), e);
                }
            }
        }
        return null;
    }

}
